#ifndef NIGHT_ARRAY_ITERATOR
#define NIGHT_ARRAY_ITERATOR

#include <iterator>

#include "core.hpp"

namespace night {

namespace array {

template< typename E >
struct iterator_wrapper {
	typedef typename detail::if_c< detail::is_const<E>::value, 
		typename E::const_iterator, 
		typename E::iterator 
	>::type iterator;

	E * ptr;
	iterator_type const it;

	iterator_wrapper ( E * src, iterator_type s ) : ptr (src), it (s) {}
	operator iterator () { return ( this->it == begin_iterator ) ? this->ptr->begin () : this->ptr->end (); }
};


template< typename Iterator, typename Category, typename Value, typename Reference, typename Difference = ptrdiff_t >
struct iterator_base : detail::Base< iterator_base<Iterator,Category,Value,Reference,Difference> >
{
	typedef Category iterator_category;

	typedef Value value_type;
	typedef Reference reference;
	typedef Value * pointer;
	typedef Difference difference_type;

	template<typename T>
	struct temporary {
		T value;
		explicit temporary ( T src ) : value (src) {}

		typename detail::remove_ref<T>::type * operator -> () { return & this->value; }
	};

	reference operator * () const { return derived<Iterator> (this)->deref (); }
	reference operator [] (difference_type) const;
	temporary<reference> operator -> () const { return temporary<reference> ( derived<Iterator> (this)->deref () ); }

	Iterator & operator ++ () { return derived<Iterator> (this)->increment (); }
	Iterator & operator -- () { return derived<Iterator> (this)->decrement (); }

	Iterator operator ++ (int) { Iterator tmp ( derived<Iterator> (*this) ); ++(*this); return tmp; }
	Iterator operator -- (int) { Iterator tmp ( derived<Iterator> (*this) ); --(*this); return tmp; }

	Iterator & operator += ( difference_type n ) { return derived<Iterator> (this)->advance (n); }
	Iterator & operator -= ( difference_type n ) { return derived<Iterator> (this)->advance(-n); }
};

template< typename Iterator,typename C,typename V,typename R,typename Difference >
inline Iterator operator + ( iterator_base<Iterator,C,V,R,Difference> const & it, Difference n ) {
	return Iterator ( derived<Iterator> (it) ) += n;
}

template< typename Iterator,typename C,typename V,typename R,typename Difference >
inline Iterator operator + ( Difference n, iterator_base<Iterator,C,V,R,Difference> const & it ) {
	return Iterator ( derived<Iterator> (it) ) += n;
}

template< typename Iterator,typename C,typename V,typename R,typename Difference >
inline Iterator operator - ( iterator_base<Iterator,C,V,R,Difference> const & it, Difference n ) {
	return Iterator ( derived<Iterator> (it) ) -= n;
}

template< typename Iterator,typename C,typename V,typename R,typename Difference >
inline Iterator operator - ( Difference n, iterator_base<Iterator,C,V,R,Difference> const & it ) {
	return Iterator ( derived<Iterator> (it) ) -= n;
}

template< 
	typename I1,typename C1,typename V1,typename R1,typename Difference, 
	typename I2,typename C2,typename V2,typename R2 >
inline Difference operator - ( iterator_base<I1,C1,V1,R1,Difference> const & i1, iterator_base<I2,C2,V2,R2,Difference> const & i2 ) {
	return derived<I2> (i2).distance( derived<I1> (i1) );
}

template< 
	typename I1,typename C1,typename V1,typename R1,typename D1, 
	typename I2,typename C2,typename V2,typename R2,typename D2 >
inline bool operator == ( iterator_base<I1,C1,V1,R1,D1> const & i1, iterator_base<I2,C2,V2,R2,D2> const & i2 ) {
	return derived<I1> (i1).equal( derived<I2> (i2) );
}

template< 
	typename I1,typename C1,typename V1,typename R1,typename D1, 
	typename I2,typename C2,typename V2,typename R2,typename D2 >
inline bool operator != ( iterator_base<I1,C1,V1,R1,D1> const & i1, iterator_base<I2,C2,V2,R2,D2> const & i2 ) {
	return ! (i1 == i2);
}

template< 
	typename I1,typename C1,typename V1,typename R1,typename Difference, 
	typename I2,typename C2,typename V2,typename R2 >
inline bool operator < ( iterator_base<I1,C1,V1,R1,Difference> const & i1, iterator_base<I2,C2,V2,R2,Difference> const & i2 ) {
	return derived<I1> (i1).distance( derived<I2> (i2) ) > 0;
}

template< 
	typename I1,typename C1,typename V1,typename R1,typename Difference, 
	typename I2,typename C2,typename V2,typename R2 >
inline bool operator > ( iterator_base<I1,C1,V1,R1,Difference> const & i1, iterator_base<I2,C2,V2,R2,Difference> const & i2 ) {
	return derived<I1> (i1).distance( derived<I2> (i2) ) < 0;
}



template< typename E, typename S >
struct iterator<E,S,1> : 
	iterator_base < iterator<E,S,1>, std::random_access_iterator_tag, typename E::value_type, typename E::result_type > 
{
	typedef typename E::size_type size_type;

	E const * expr;
	size_type s;
	size_type i;

	explicit iterator ( E const & src, size_type n = 0 ) : expr (&src), s (n), i (n) {}

	iterator & increment () { ++this->s; ++this->i; return (*this); }
	iterator & decrement () { --this->s; --this->i; return (*this); }
	typename E::result_type deref () const { return (*this->expr) (this->i); }

	bool equal ( iterator const & it ) const { return this->expr==it.expr && this->s==it.s; }

	iterator & advance ( ptrdiff_t n ) { 
		this->s += n;
		this->i = this->s;
		return *this; 
	}
	ptrdiff_t distance ( iterator const & it ) const { return it.s - this->s; }
};

template< typename E, typename S >
struct iterator<E,S,2> : 
	iterator_base < iterator<E,S,2>, std::random_access_iterator_tag, typename E::value_type, typename E::result_type > 
{
	typedef typename E::size_type size_type;

	E const * expr;
	size_type s;
	size_type i;
	size_type j;

	explicit iterator ( E const & src, size_type n = 0 ) : expr (&src), s (n) { this->advance (0); }
	explicit iterator ( E const & src, size_type x, size_type y ) : expr (&src), s ( x * src.size(1) + y ), i(x), j(y) {}

	iterator & increment () { 
		++this->s; 
		++this->j;
		if ( this->j == this->expr->size (1) ) { this->j = 0; ++this->i; }
		return (*this);
	}
	iterator & decrement () { 
		if ( this->j == 0 ) { this->j = this->expr->size (1); --this->i; }
		--this->j; 
		--this->s;
		return (*this); 
	}
	typename E::result_type deref () const { return (*this->expr) (this->i,this->j); }

	bool equal ( iterator const & it ) const { return this->expr==it.expr && this->s==it.s; }

	iterator & advance ( ptrdiff_t n ) {
		this->s += n;
		this->i = this->s / this->expr->size (1);
		this->j = this->s % this->expr->size (1);
		return *this; 
	}
	ptrdiff_t distance ( iterator const & it ) const { return it.s - this->s; }
};

template< typename E, typename S >
struct iterator<E,S,3> : 
	iterator_base < iterator<E,S,3>, std::random_access_iterator_tag, typename E::value_type, typename E::result_type > 
{
	typedef typename E::size_type size_type;

	E const * expr;
	size_type s;
	size_type i;
	size_type j;
	size_type k;

	explicit iterator ( E const & src, size_type n = 0 ) : expr (&src), s (n) { this->advance (0); }
	explicit iterator ( E const & src, size_type x, size_type y, size_type z ) : 
		expr (&src), s ( x * src.size(2) * src.size(1) + y * src.size(1) + z ), i (x), j (y), k (z) {}

	iterator & increment () {
		++this->s;
		++this->k;
		if ( this->k == this->expr->size (2) ) {
			this->k = 0; ++this->j;
			if ( this->j == this->expr->size (1) ) { this->j = 0; ++this->i; }
		}
		return (*this);
	}
	iterator & decrement () {
		if ( this->k == 0 ) {
			if ( this->j == 0 ) { this->j = this->expr->size (1); --this->i; }
			--this->j; this->k = this->expr->size (2);
		}
		--this->k;
		--this->s;
		return (*this);
	}
	typename E::result_type deref () const { return (*this->expr) (this->i,this->j,this->k); }

	bool equal ( iterator const & it ) const { return this->expr==it.expr && this->s==it.s; }

	iterator & advance ( ptrdiff_t n ) {
		this->s += n;
		this->i = this->s / this->expr->size (2) / this->expr->size (1); 
		this->j = this->s / this->expr->size (2) % this->expr->size (1);
		this->k = this->s % this->expr->size (2);
		return *this; 
	}
	ptrdiff_t distance ( iterator const & it ) const { return it.s - this->s; }
};

} // end namespace night::array

} // end namespace night

#endif
